#include "api.h"
#include <newcode/encoder/EncoderGC.h>
#include <string>
#include <cstdlib>
#include <newcode/GreyscaleCodeReader.h>
#include <zxing/NotFoundException.h>
#include <zxing/FormatException.h>
#include <cstring>

using namespace std;
using namespace newcode;
using namespace newcode::encoder;

typedef char ncchar_t;

static unsigned char** byteMatrixCopy(Ref<ByteMatrix> matrix)
{
    unsigned char** data = (unsigned char**)calloc(matrix->getY(),
        sizeof(unsigned char*));
    for (int i = 0; i < matrix->getY(); i++)
    {
        data[i] = (unsigned char*)calloc(matrix->getX(), sizeof(unsigned char));
        for (int j = 0; j < matrix->getX(); j++)
        {
            data[i][j] = matrix->operator()(j, i);
        }
    }
    return data;
}

static unsigned char** effectiveEncode(ncchar_t* dataStr,
    unsigned char* dataBytes, NC_Mode mode, int len, int* size, NC_Error* error)
{
    NC_Error dummyError;
    if (error == NULL)
    {
        error = &dummyError;
    }
    Ref<ByteMatrix> result(NULL);
    try
    {
        Encoder encoder;
        if (len == -1)
        {
            result = encoder.encode(String(dataStr), (EncodeMode)mode);
        }
        else
        {
            result = encoder.encode(ArrayRef<unsigned char>(dataBytes, len),
                (EncodeMode)mode);
        }
        *size = result->getX();
        error->code = ERROR_NO_ERRORS;
        return byteMatrixCopy(result);
    }
    catch(exception &e)
    {
        error->code = ERROR_UNNAMED;
        error->what = e.what();
    }
    catch(...)
    {
        error->code = ERROR_UNNAMED;
        error->what = NULL;
    }
    return NULL;
}

NC_API unsigned char**
nc_encode(ncchar_t* data, NC_Mode mode, int* size, NC_Error* error)
{
    return effectiveEncode(data, NULL, mode, -1, size, error);
}

NC_API unsigned char**
nc_encode_raw(unsigned char* data, int len,
    NC_Mode mode,  int* size, NC_Error* error)
{
    return effectiveEncode(NULL, data, mode, len, size, error);
}

NC_API NC_DecodeResult*
nc_decode(unsigned char** image, int sizeX, int sizeY, NC_Error* error)
{
    NC_Error dummyError;
    if (error == NULL)
    {
        error = &dummyError;
    }
    try
    {
        GreyscaleCodeReader reader;
        Ref<ByteMatrix> imageMatrix(new ByteMatrix(sizeX, sizeY));
        for (int x = 0; x < sizeX; x++)
        {
            for(int y = 0; y < sizeY; y ++)
            {
                (*imageMatrix)(x, y) = image[y][x];
            }
        }
        Ref<Result> result = reader.decode(imageMatrix);

        NC_DecodeResult* podResult = new NC_DecodeResult;

        if (result->getText() != NULL)
        {
            const ncchar* rawText = result->getText()->getText().c_str();
            size_t rawTextSize = sizeof(ncchar_t) * strlen(rawText) + 1;
            podResult->text = new ncchar_t[rawTextSize];
            podResult->text = strcpy(podResult->text, rawText);
        }
        else
        {
            podResult->text = NULL;
        }

        unsigned char* rawBytes = &(result->getRawBytes()[0]);
        size_t rawBytesSize = result->getRawBytes().size();
        podResult->rawBytes = new unsigned char[rawBytesSize];
        memcpy(podResult->rawBytes, rawBytes, rawBytesSize);

        for (int i = 0; i < 4; i++)
        {
            podResult->points[2 * i] = result->getResultPoints()[i]->getX();
            podResult->points[2 * i + 1] = result->getResultPoints()[i]->getY();
        }

        error->code = ERROR_NO_ERRORS;
        return podResult;
    }
    catch (NotFoundException const& ex)
    {
        error->code = ERROR_NOT_FOUNND;
        error->what = ex.what();
    }
    catch (FormatException const& ex)
    {
        error->code = ERROR_BAD_FORMAT;
        error->what = ex.what();
    }
    catch (exception const& ex)
    {
        error->code = ERROR_UNNAMED;
        error->what = ex.what();
    }
    catch (...)
    {
        error->code = ERROR_UNNAMED;
        error->what = NULL;
    }
    return NULL;
}

NC_API void nc_release_result(NC_DecodeResult* result)
{
    delete[] result->rawBytes;
    delete[] result->text;
    delete result;
}
